package besta.moon.sql.expression.function.time;

import besta.moon.sql.Expression;
import besta.moon.sql.datatype.DateTime;
import besta.moon.sql.datatype.MutableNumeric;
import besta.moon.sql.expression.function.ExpressionFunction;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.util.MoonUtils;

public class FunctionTimestampDiff extends ExpressionFunction {

    final private int interval;

    static final int mapIntervalType(int intervalType) {
        return switch (intervalType) {
            case SQLTokenizer.MILLISECOND ->
                SQLTokenizer.SQL_TSI_FRAC_SECOND;
            case SQLTokenizer.SECOND ->
                SQLTokenizer.SQL_TSI_SECOND;
            case SQLTokenizer.MINUTE ->
                SQLTokenizer.SQL_TSI_MINUTE;
            case SQLTokenizer.HOUR ->
                SQLTokenizer.SQL_TSI_HOUR;
            case SQLTokenizer.D, SQLTokenizer.DAY ->
                SQLTokenizer.SQL_TSI_DAY;
            case SQLTokenizer.WEEK ->
                SQLTokenizer.SQL_TSI_WEEK;
            case SQLTokenizer.MONTH ->
                SQLTokenizer.SQL_TSI_MONTH;
            case SQLTokenizer.QUARTER ->
                SQLTokenizer.SQL_TSI_QUARTER;
            case SQLTokenizer.YEAR ->
                SQLTokenizer.SQL_TSI_YEAR;
            default ->
                intervalType;
        };
    }

    public FunctionTimestampDiff(int intervalType, Expression p1, Expression p2) {
        interval = mapIntervalType(intervalType);
        setParams(new Expression[]{p1, p2});
    }

    @Override
    public int getFunction() {
        return SQLTokenizer.TIMESTAMPDIFF;
    }

    @Override
    public boolean isNull() throws Exception {
        return param1.isNull() || param2.isNull();
    }

    @Override
    public boolean getBoolean() throws Exception {
        return getInt() != 0;
    }

    @Override
    public int getInt() throws Exception {
        if (isNull()) {
            return 0;
        }
        switch (interval) {
            case SQLTokenizer.SQL_TSI_FRAC_SECOND -> {
                return (int) (param2.getLong() - param1.getLong());
            }
            case SQLTokenizer.SQL_TSI_SECOND -> {
                return (int) (param2.getLong() / 1000 - param1.getLong() / 1000);
            }
            case SQLTokenizer.SQL_TSI_MINUTE -> {
                return (int) (param2.getLong() / 60000 - param1.getLong() / 60000);
            }
            case SQLTokenizer.SQL_TSI_HOUR -> {
                return (int) (param2.getLong() / 3600000 - param1.getLong() / 3600000);
            }
            case SQLTokenizer.SQL_TSI_DAY -> {
                return (int) (param2.getLong() / 86400000 - param1.getLong() / 86400000);
            }
            case SQLTokenizer.SQL_TSI_WEEK -> {
                long day2 = param2.getLong() / 86400000;
                long day1 = param1.getLong() / 86400000;
                return (int) ((day2 + 3) / 7 - (day1 + 3) / 7);
            }
            case SQLTokenizer.SQL_TSI_MONTH -> {
                DateTime.Details details2 = new DateTime.Details(param2.getLong());
                DateTime.Details details1 = new DateTime.Details(param1.getLong());
                return (details2.year * 12 + details2.month) - (details1.year * 12 + details1.month);
            }
            case SQLTokenizer.SQL_TSI_QUARTER -> {
                DateTime.Details details2 = new DateTime.Details(param2.getLong());
                DateTime.Details details1 = new DateTime.Details(param1.getLong());
                return (details2.year * 4 + details2.month / 3) - (details1.year * 4 + details1.month / 3);
            }
            case SQLTokenizer.SQL_TSI_YEAR -> {
                DateTime.Details details2 = new DateTime.Details(param2.getLong());
                DateTime.Details details1 = new DateTime.Details(param1.getLong());
                return details2.year - details1.year;
            }
            default ->
                throw new Error();
        }
    }

    @Override
    public long getLong() throws Exception {
        return getInt();
    }

    @Override
    public float getFloat() throws Exception {
        return getInt();
    }

    @Override
    public double getDouble() throws Exception {
        return getInt();
    }

    @Override
    public long getMoney() throws Exception {
        return getInt() * 10000L;
    }

    @Override
    public MutableNumeric getNumeric() throws Exception {
        if (isNull()) {
            return null;
        }
        return new MutableNumeric(getInt());
    }

    @Override
    public Object getObject() throws Exception {
        if (isNull()) {
            return null;
        }
        return MoonUtils.getInteger(getInt());
    }

    @Override
    public String getString() throws Exception {
        if (isNull()) {
            return null;
        }
        return String.valueOf(getInt());
    }

    @Override
    public int getDataType() {
        return SQLTokenizer.INT;
    }

}
